﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vowei.Data.Models.Statistics;
using Vowei.Data.Helpers;
using Vowei.Core.Models;
using Vowei.Data.Models;
using System.Diagnostics;
using Vowei.Core.Resource;

namespace Vowei.Data.Helpers
{
    // TODO: 这个类的代码最好都写在数据库的存储过程里
    public static class StatisticsHelpers
    {
        public static RequirementStatistics[] TopRequirementList(this Project project, IVoweiContext context)
        {
            if (project == null)
                throw new NullReferenceException();
            if (context == null)
                throw new ArgumentNullException("context");
            var topRequirements = project.TopRequirements(context);
            var result = new List<RequirementStatistics>();

            foreach (var req in topRequirements)
            {
                var tickets = req.AllTickets(context);
                var tasks = tickets.OfType<Task>();
                var reqs = tickets.OfType<Requirement>();
                var rw = new RequirementStatistics()
                {
                    Requirement = req,
                    ActiveTicketsCount = tickets.Active().Count(),
                    ClosedTicketsCount = tickets.Closed().Count(),
                    ActiveTasksCount = tasks.Active().Count(),
                    ClosedTasksCount = tasks.Closed().Count(),
                    TotalBusinessValue = req.BusinessValue,
                    EarnedBusinessValue = reqs.Closed().Sum(r => r.BusinessValue),
                    TotalEstimatedDuration = TimeSpan.FromTicks(tasks.Sum(r => r.DueDate.Value.Ticks - r.StartDate.Value.Ticks)),
                    CompletedWorkHours = TimeSpan.FromTicks(tasks.Closed().Sum(r => r.DueDate.Value.Ticks - r.StartDate.Value.Ticks)),
                    ActiveRequirementsCount = reqs.Where(r => !r.ClosedDate.HasValue).Count(),
                    ClosedRequirementsCount = reqs.Where(r => r.ClosedDate.HasValue).Count()
                };

                result.Add(rw);
            }

            return result.ToArray();
        }

        public static RequirementWorks[] TopRequirementWorks(this Project project, IVoweiContext context)
        {
            if (project == null)
                throw new NullReferenceException();
            if (context == null)
                throw new ArgumentNullException("context");

            var result = new List<RequirementWorks>();
            var milestones = project.Children;
            var topRequirements = project.TopRequirements(context);

            foreach (var req in topRequirements)
            {
                var tickets = req.AllTickets(context);
                var tasks = tickets.OfType<Task>();
                var reqs = tickets.OfType<Requirement>();
                var rw = new RequirementWorks()
                {
                    Requirement = req,
                    ActiveTasksCount = tasks.Where(t => !t.ClosedDate.HasValue).Count(),
                    ClosedTasksCount = tasks.Where(t => t.ClosedDate.HasValue).Count(),
                    ActiveRequirementsCount = reqs.Where(r => !r.ClosedDate.HasValue).Count(),
                    ClosedRequirementsCount = reqs.Where(r => r.ClosedDate.HasValue).Count()
                };

                result.Add(rw);
            }

            return result.ToArray();
        }

        public static DateTimeWorkStatistics[] EarnedValuesObsolete(this Project project, IVoweiContext context)
        {
            if (project == null)
                throw new NullReferenceException();
            if (context == null)
                throw new ArgumentNullException("context");

            var tickets = project.AllTickets(context);
            int tasksCount = 0;
            int reqsCount = 0;
            var result = new List<DateTimeWorkStatistics>();
            var groups = tickets.GroupBy(
                t => t.ClosedDate.HasValue ? t.ClosedDate.Value.Date : project.StartDate.Value);

            foreach (var group in groups)
            {
                DateTimeWorkStatistics item = new DateTimeWorkStatistics();
                if (group.Key == project.StartDate.Value)
                {
                    item.Date = project.StartDate.Value;
                    item.ActiveTasksCount = group.OfType<Task>().Count();
                    item.ActiveRequirementsCount = group.OfType<Requirement>().Count();
                    tasksCount = item.ActiveTasksCount;
                    reqsCount = item.ActiveRequirementsCount;
                }
                else
                {
                    item.Date = group.Key;
                    item.ClosedTasksCount = group.OfType<Task>().Count();
                    item.ClosedRequirementsCount = group.OfType<Requirement>().Count();

                    tasksCount -= item.ClosedTasksCount;
                    item.ActiveTasksCount = tasksCount;

                    reqsCount -= item.ClosedRequirementsCount;
                    item.ActiveRequirementsCount = reqsCount;
                }

                result.Add(item);
            }

            return result.OrderBy(d => d.Date).ToArray();
        }

        class RequirementDueDate
        {
            public Requirement Requirement { get; set; }

            public DateTime Date { get; set; }
        }

        // TODO: 当实现资源的可用时间和资源挂钩以后，移除这个函数
        private static ResourceCalendar CreateStandardResourceCalendar()
        {
            var calendar = new DbResourceCalendar("Standard");
            var workweek = new WorkWeek("Default")
            {
                EffectPeriod = new Period { Start = DateTime.MinValue, Finish = DateTime.MaxValue }
            };

            workweek.Days.Add(new WorkDay(DayOfWeek.Sunday)
            {
                Type = WorkingTimeType.Nonworking
            });

            var am8 = new DateTime(1, 1, 1, 9, 0, 0);
            var pm12 = new DateTime(1, 1, 1, 12, 0, 0);
            var pm1 = new DateTime(1, 1, 1, 13, 0, 0);
            var pm5 = new DateTime(1, 1, 1, 18, 0, 0);

            for (var d = DayOfWeek.Monday; d < DayOfWeek.Saturday; ++d)
            {
                var day = new WorkDay(d)
                {
                    Type = WorkingTimeType.Working
                };

                day.Periods.Add(new Period { Start = am8, Finish = pm12 });
                day.Periods.Add(new Period { Start = pm1, Finish = pm5 });
                workweek.Days.Add(day);
            }

            workweek.Days.Add(new WorkDay(DayOfWeek.Saturday)
            {
                Type = WorkingTimeType.Nonworking
            });

            calendar.WorkWeeks.Add(workweek);
            return calendar;
        }

        // TODO: 代码需要重构，太长了！
        public static EarnedValueStatistics[] EarnedValues(this Project project, IVoweiContext context)
        {
            if (project == null)
                throw new NullReferenceException();
            if (context == null)
                throw new ArgumentNullException("context");

            // TODO: 实现对工时的支持以后，将下面的判断语句去掉
            if (project.ProjectBaseCalendar == null)
                project.ProjectBaseCalendar = CreateStandardResourceCalendar();

            var tickets = project.AllTickets(context);
            var requirements = tickets.OfType<Requirement>();
            var topReqs = requirements.Where(r => r.ParentId == null);
            var leafReqs = requirements.LeafRequirements();
            var finishedReqs = requirements
                .GroupBy(
                     r => r.ClosedDate.HasValue ? r.ClosedDate.Value.Date : DateTime.MaxValue)
                .OrderBy(g => g.Key);
            var reqDues = new SortedList<DateTime, List<Requirement>>(requirements.Count());

            foreach (var req in requirements)
            {
                var subtasks = req.AllTasks(context);
                if (subtasks.Count() > 0)
                {
                    var date = subtasks.Max(t => t.DueDate.HasValue ? t.DueDate.Value.Date : DateTime.MaxValue);
                    List<Requirement> lrdd = null;

                    if (reqDues.ContainsKey(date))
                        lrdd = reqDues[date];
                    else
                    {
                        lrdd = new List<Requirement>();
                        reqDues.Add(date, lrdd);
                    }

                    lrdd.Add(req);
                }
            }
            
            var evs = new SortedList<DateTime, EarnedValueStatistics>();
            var currentBcwp = 0.0;
            foreach (var rdd in reqDues)
            {
                var date = rdd.Key.Date;
                var ev = FindOrNew(evs, date);

                var reqs = rdd.Value;

                foreach (var req in reqs)
                {
                    var duedReqs = reqDues.Where(r => r.Key <= date).SelectMany(kvp => kvp.Value);
                    var childReqs = req.AllRequirements(context).Intersect(duedReqs);
                    currentBcwp += req.BusinessValue - childReqs.Sum(r => r.BusinessValue);
                }

                ev.BCWP = currentBcwp;
            }

            var currentBcws = 0.0;
            foreach (var fr in finishedReqs)
            {
                var date = fr.Key;
                var ev = FindOrNew(evs, date);

                foreach (var req in fr)
                {
                    var childReqs = req.AllRequirements(context).Intersect(requirements.Where(r => r.ClosedDate.HasValue && r.ClosedDate.Value <= date));
                    currentBcws += req.BusinessValue - childReqs.Sum(r => r.BusinessValue);
                }

                ev.BCWS = currentBcws;
            }

            var tasks = tickets.OfType<Task>();
            var finishedTasks = tasks
                .GroupBy(
                     t => t.ClosedDate.HasValue ? t.ClosedDate.Value.Date : DateTime.MaxValue)
                .OrderBy(g => g.Key);
            var taskDues = tasks
                .GroupBy(
                     t => t.DueDate.HasValue ? t.DueDate.Value.Date : DateTime.MaxValue)
                .OrderBy(g => g.Key);
            
            // TODO: 当实现资源的可用时间和资源挂钩以后，这个值需要从项目组的日历来确定
            var calendar = CreateStandardResourceCalendar();
            var finishHours = TimeSpan.Zero;
            var planHours = TimeSpan.Zero;
            var assignmentCache = new Dictionary<string, ResourceAssignment>();

            foreach (var ft in finishedTasks)
            {
                if (ft.Key == DateTime.MaxValue)
                    break;

                var date = ft.Key;
                var ev = FindOrNew(evs, date);

                foreach (var task in ft)
                {
                    var calc = GetWorkingHoursCalculator(project, context, calendar, assignmentCache, task);
                    finishHours += calc.CalcWorkingHours(task.StartDate.Value, task.DueDate.Value);
                    ev.FinishedWorkHours = finishHours;
                }
            }

            var currentAcwp = 0.0;
            foreach (var td in taskDues)
            {
                if (td.Key == DateTime.MaxValue)
                    break;

                var date = td.Key;
                var ev = FindOrNew(evs, date);

                foreach (var task in td)
                {
                    var calc = GetWorkingHoursCalculator(project, context, calendar, assignmentCache, task);
                    planHours += calc.CalcWorkingHours(task.StartDate.Value, task.DueDate.Value);
                    currentAcwp += CalcActualCost(project, task, context, calendar);
                    ev.PlanedWorkHours = planHours;
                    ev.ACWP = currentAcwp;
                }
            }

            for (int i = 1; i < evs.Values.Count; ++i)
            {
                var ev = evs.Values[i];
                var previousEv = evs.Values[i - 1];

                if (ev.BCWP == 0.0)
                    ev.BCWP = previousEv.BCWP;
                if (ev.BCWS == 0.0)
                    ev.BCWS = previousEv.BCWS;
                if (ev.ACWP == 0.0)
                    ev.ACWP = previousEv.ACWP;
                if (ev.PlanedWorkHours == TimeSpan.Zero)
                    ev.PlanedWorkHours = previousEv.PlanedWorkHours;
                if (ev.FinishedWorkHours == TimeSpan.Zero)
                    ev.FinishedWorkHours = previousEv.FinishedWorkHours;
            }

            return evs.Values.ToArray();
        }

        private static WorkingHourCalculator GetWorkingHoursCalculator(Project project, IVoweiContext context, ResourceCalendar calendar, Dictionary<string, ResourceAssignment> assignmentCache, Task task)
        {
            var assignment = FindAssignment(context, project, task.AssignedTo, assignmentCache);

            WorkingHourCalculator calc = null;
            if (assignment != null && assignment.WorkCalendar != null)
                calc = new WorkingHourCalculator(assignment);
            else
                calc = new WorkingHourCalculator(calendar);

            return calc;
        }

        private static ResourceAssignment FindAssignment(IVoweiContext context, Project project, string owner, Dictionary<string, ResourceAssignment> assignmentCache)
        {
            if (string.IsNullOrEmpty(owner))
                return null;

            if (assignmentCache.ContainsKey(owner))
                return assignmentCache[owner];
            else
            {
                var employee = context.Employees.Query.SingleOrDefault(e => e.Email == owner || e.UserName == owner);
                if (employee == null)
                    throw new InvalidOperationException(string.Format("在系统数据库中无法找到任务的负责人 [{0}]!", owner));

                var assignment = project.ResourceAssignments.Single(a => a.ResourceId == employee.ID); // context.Assignments.Query.Single(a => a.ResourceId == employee.ID && a.ProjectId == project.ID);
                assignmentCache.Add(owner, assignment);
                return assignment;
            }
        }

        private static double CalcActualCost(Project project, Task task, IVoweiContext context, ResourceCalendar calendar)
        {
            var owner = task.AssignedTo;
            var employee = context.Employees.Query.SingleOrDefault(e => e.Email == owner || e.UserName == owner);
            if (employee == null)
            {
                Trace.TraceWarning(string.Format("找不到用户名或者email为 [{0}] 的用户！", owner));
                return 0.0;
            }
            else
            {
                var assignment = project.ResourceAssignments.FirstOrDefault(a => a.ResourceId == employee.ID && a.ProjectId == project.ID);
                if (assignment != null)
                {
                    // TODO: 添加了对工时的支持后，将下面的注释取消掉
                    var calc = new WorkingHourCalculator(calendar, assignment);
                    var hours = task.ClosedDate.HasValue ? calc.CalcWorkingHours(task.StartDate.Value, task.ClosedDate.Value)
                                                         : calc.CalcWorkingHours(task.StartDate.Value, task.DueDate.Value);
                    return hours.TotalHours * assignment.CostPerHour;
                }
                else
                {
                    throw new InvalidOperationException(string.Format("用户 [{0}] 在wbs或者任务分配项中有被分配的任务，然而在项目 [{1}] 的成员信息中，却没有该用户的信息！", employee.Email, project.Title));
                }
            }
        }

        public static IEnumerable<Ticket> PlanDetails(this Project project, DateTime date, IVoweiContext context)
        {
            if (project == null)
                throw new NullReferenceException();
            if ( context == null)
                throw new ArgumentNullException("context");

            var alltickets = project.AllTickets(context);
            var requirements = alltickets.OfType<Requirement>();
            var tasks = alltickets.OfType<Task>();
            var reqDues = new SortedList<DateTime, List<Requirement>>(requirements.Count());

            foreach (var req in requirements)
            {
                var subtasks = req.AllTasks(context);
                if (subtasks.Count() > 0)
                {
                    var duedate = subtasks.Max(t => t.DueDate.HasValue ? t.DueDate.Value.Date : DateTime.MaxValue);
                    List<Requirement> lrdd = null;

                    if (reqDues.ContainsKey(duedate))
                        lrdd = reqDues[duedate];
                    else
                    {
                        lrdd = new List<Requirement>();
                        reqDues.Add(duedate, lrdd);
                    }

                    lrdd.Add(req);
                }
            }

            var result = new List<Ticket>();
            result.AddRange(
                tasks.Where(t =>
                    (t.DueDate.HasValue ? t.DueDate.Value.Date == date : false) || 
                    (t.ClosedDate.HasValue ? t.ClosedDate.Value.Date == date : false)));
            result.AddRange(
                requirements.Where(r =>
                    r.ClosedDate.HasValue ? r.ClosedDate.Value.Date == date : false));
            if (reqDues.ContainsKey(date))
            {
                result.AddRange(reqDues[date]);
            }

            return result.Distinct();
        }

        private static EarnedValueStatistics FindOrNew(SortedList<DateTime, EarnedValueStatistics> evs, DateTime date)
        {
            EarnedValueStatistics ev = null;
            if (evs.ContainsKey(date))
                ev = evs[date];
            else
            {
                ev = new EarnedValueStatistics() { Date = date };
                evs.Add(date, ev);
            }
            return ev;
        }
    }
}
